/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package org.javaongems.runtime.io;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;

/**
 * <p>Provide unix path utility features for GWT applications that are not part 
 * 		of the JDK or commons IO.</p>
 * <p>Responsibilities:
 * <ul>
 * 	<li> convert into a unix style path
 * 	<li> toggle a path to either relative or fully qualified
 * 	<li> append & prepend slashes
 * 	<li> query name, parent path and extension elements
 * </ul>
 * </p>
 * 
 * @see org.javaongems.client.rt.io.PathUtils (gwt emulation constraint)
 * @author Ashin Wimalajeewa (ash)
 */
final public class PathUtils {
	final static public char 	BACK_SLASH_AS_CHAR = '\\';
	final static public String 	BACK_SLASH= BACK_SLASH_AS_CHAR + "";
	final static public char 	FORWARD_SLASH_AS_CHAR = '/';
	final static public String 	FORWARD_SLASH = FORWARD_SLASH_AS_CHAR + "";
	final static public String 	UP_LEVEL = "..";
	final static public char 	CURRENT_LEVEL_AS_CHAR = '.';
	final static public String 	CURRENT_LEVEL = CURRENT_LEVEL_AS_CHAR + "";
	final static public String 	CURRENT_LEVEL_FORWARD_SLASH = CURRENT_LEVEL + FORWARD_SLASH;

	private PathUtils() {
    	// nothing to do.
	}
	
	/**
	 * Casts a given path into the unix slash format
	 * @param path - path to re-style
	 * @return - path with no back slashes
	 */
	static public String toUnixStylePath(String path) {
		return FilenameUtils.separatorsToUnix(path);
	}

	/**
	 * Convert a path into a relative path based given a fully qualified path
	 * @param fullyQualBaseLoc - fully qualified base location
	 * @param fullyQualCurrentLoc - fully qualified current location
	 * @return a relative path from the base location
	 */
	static public String toRelativePath(String fullyQualBaseLoc,
			String fullyQualCurrentLoc) {
		return getRelativePath(fullyQualBaseLoc, fullyQualCurrentLoc, FORWARD_SLASH_AS_CHAR);
	}

	/**
	 * @param currLoc - fully qualified current location 
	 * @param relativeLoc - a path relative to the current location
	 * @return a fully qualified representation of the relative reference
	 * @throws IllegalArgumentException
	 */
	static public String toFullyQualified(String currLoc, String relativeLoc)
			throws IllegalArgumentException {
		if ((currLoc == null) || (relativeLoc == null))
			throw new IllegalArgumentException("non-null arguments required");
		String ret;
		String nixCurrLoc = FilenameUtils.separatorsToUnix(currLoc);
		String nixRelativeDoc = FilenameUtils.separatorsToUnix(relativeLoc);
		if (relativeLoc.startsWith(FORWARD_SLASH)) { 
			ret = FilenameUtils.normalize(nixRelativeDoc);
		} else {
			ret = appendSlashIfRequired(nixCurrLoc) + nixRelativeDoc;
			ret = FilenameUtils.normalize(ret);
		}
		return ret == null ? FORWARD_SLASH: FilenameUtils.separatorsToUnix(ret);
	}

	/**
	 * Query the parent path of the given location
	 * @param fullyQualifiedPath - a fully qualified path
	 * @return the parent path
	 */
	static public String toParentPath(String fullyQualifiedPath) {
		if (fullyQualifiedPath.equals(FORWARD_SLASH))
			return fullyQualifiedPath;
		String ret = FilenameUtils.getFullPath(fullyQualifiedPath);
		if (StringUtils.isEmpty(ret))
			return FORWARD_SLASH;
		ret = FilenameUtils.separatorsToUnix(ret);
		return ret;
	}

	/**
	 * Command that adds a slash to the end of a path
	 * @param path - the path to append a slash to 
	 * @return a path which ends with a slash
	 */
	static public String appendSlashIfRequired(String path) {
		if (path.endsWith(FORWARD_SLASH))
			return path;
		return path + FORWARD_SLASH;
	}

	/**
	 * Command that adds a slash to the start of a path
	 * @param path - the path to prepend a slash to
	 * @return a path which starts with a slash
	 */
	static public String prependSlashIfRequired(String path) {
		if (path.startsWith(FORWARD_SLASH))
			return path;
		return FORWARD_SLASH + path;
	}

	/**
	 * Command that removes a slash from a given path
	 * @param path - the path to manipulate
	 * @param prefix - whether it is to be removed from the start or end
	 * @param ignoreIfRoot - whether to ignore the removal if it is the root path
	 * @return a path with the specified slash removed from it
	 */
	static public String removeSlashIfRequired(String path, boolean prefix, boolean ignoreIfRoot) {
		int len = path.length();
		boolean ignore = (ignoreIfRoot && len == 1);
		if (prefix) {
			if (path.startsWith(FORWARD_SLASH) && !ignore) 
				path = path.substring(1);
		} else {
			if (path.endsWith(FORWARD_SLASH) && !ignore)
				path = path.substring(0, len - 1);
		}
		return path;
	}


	/**
	 * break a path down into individual elements and add to a list. example :
	 * if a path is /a/b/c/d.txt, the breakdown will be [d.txt,c,b,a]
	 * 
	 * @param f
	 *            input file
	 * @return a List collection with the individual elements of the path in
	 *         reverse order
	 */
	private static List getPathList(File f) {
		List l = new ArrayList();
		File r;
		try {
			r = f.getCanonicalFile();
			while (r != null) {
				l.add(r.getName());
				r = r.getParentFile();
			}
		} catch (IOException e) {
			e.printStackTrace();
			l = null;
		}
		return l;
	}

	/**
	 * figure out a string representing the relative path of 'f' with respect to
	 * 'r'
	 * 
	 * @param r
	 *            home path
	 * @param f
	 *            path of file
	 */
	private static String matchPathLists(List r, List f, char separator) {
		int i;
		int j;
		StringBuffer s = new StringBuffer();
		// start at the beginning of the lists
		// iterate while both lists are equal
		i = r.size() - 1;
		j = f.size() - 1;

		// first eliminate common root
		while ((i >= 0) && (j >= 0) && (r.get(i).equals(f.get(j)))) {
			i--;
			j--;
		}

		// for each remaining level in the home path, add a ..
		for (; i >= 0; i--) {
			s.append(UP_LEVEL).append(separator);
		}

		// for each level in the file path, add the path
		for (; j >= 1; j--) {
			s.append(f.get(j)).append(separator);
		}

		if (s.length() == 0)
			s.append(CURRENT_LEVEL_FORWARD_SLASH);
		if (j > -1) 
			s.append(f.get(j)); // file name
		return s.toString();
	}

	/**
	 * get relative path of File 'f' with respect to 'home' directory example :
	 * home = /a/b/c f = /a/d/e/x.txt s = getRelativePath(home,f) =
	 * ../../d/e/x.txt
	 * 
	 * @author David M. Howard (from http://www.devx.com/tips/Tip/13737)
	 * @param home
	 *            base path, should be a directory, not a file, or it doesn't
	 *            make sense
	 * @param f
	 *            file to generate path for
	 * @return path from home to f as a string
	 */
	private static String getRelativePath(File home, File f, char separator ) {

		final List homelist = getPathList(home);
		final List filelist = getPathList(f);
		final String s = matchPathLists(homelist, filelist, separator );

		return s;
	}

	private static String getRelativePath(String home, String f, char separator) {
		return getRelativePath( new File(home), new File(f), separator );
	}
}